目录
  1. 1. 一、系统定位
    1. 1.1. 能力边界
  2. 2. 二、核心文件地图
  3. 3. 三、Feature 门控机制(gates.ts)
    1. 3.1. Chicago Config 数据结构
    2. 3.2. 开关读取链
    3. 3.3. 订阅等级要求
  4. 4. 四、MCP 集成架构(setup.ts + mcpServer.ts)
    1. 4.1. 为什么用 MCP 而不是内置工具?
    2. 4.2. MCP 配置构建流程
  5. 5. 五、执行器核心(executor.ts)
    1. 5.1. 截图流水线
    2. 5.2. 终端窗口排除
    3. 5.3. 剪贴板操作
  6. 6. 六、坐标系统
    1. 6.1. 两种坐标模式
    2. 6.2. 坐标转换链
  7. 7. 七、安全机制
    1. 7.1. 并发锁(computerUseLock.ts)
    2. 7.2. ESC 紧急中止(escHotkey.ts)
    3. 7.3. TCC(系统隐私权限)
    4. 7.4. 会话清理(cleanup.ts)
  8. 8. 八、与工具系统的集成
  9. 9. 九、关键设计决策
  10. 10. 涉及源文件
【Claude Code源码剖析】24-桌面自动化子系统

⚠️ 学习声明:本文档基于 Claude Code 2.1.88 源码分析整理,仅供个人学习研究使用,不做任何商业用途。

内部代号:Chicago(Feature Flag: tengu_malort_pedway
这是 CC 的桌面控制能力——让 Claude 像人一样操控鼠标、键盘、截图分析 GUI 应用。


一、系统定位

能力边界

能力 实现层 平台
鼠标移动/点击 @ant/computer-use-input(Rust/enigo) macOS
键盘输入 @ant/computer-use-input(Rust/enigo) macOS
截图 @ant/computer-use-swift(Swift/SCContentFilter) macOS
剪贴板读写 pbcopy/pbpaste 系统命令 macOS
前台应用检测 @ant/computer-use-swift(NSWorkspace) macOS

CLI vs Desktop(Cowork)的关键差异

差异点 Cowork(Electron) CLI(Claude Code)
点击穿透 withClickThrough + setIgnoreMouseEvents 无窗口,直接操作
宿主 BundleID Electron 应用 BundleID 哨兵值 CLI_HOST_BUNDLE_ID(永不匹配)
剪贴板 Electron clipboard 模块 pbcopy/pbpaste
截图排除 排除自身 Electron 窗口 排除终端模拟器窗口

二、核心文件地图

src/utils/computerUse/
├── common.ts # 常量与工具函数(BundleID 检测)
├── gates.ts # Feature 开关(Chicago 门控)
├── executor.ts # 核心执行器(实现 ComputerExecutor 接口)
├── setup.ts # MCP 服务配置构建
├── mcpServer.ts # In-Process MCP Server(拦截 stdio 请求)
├── hostAdapter.ts # 宿主适配器
├── swiftLoader.ts # Swift 原生模块懒加载
├── inputLoader.ts # Input(Rust)原生模块懒加载
├── escHotkey.ts # ESC 紧急中止热键
├── computerUseLock.ts # 并发锁(同一时刻只允许一个 CU 操作)
├── cleanup.ts # 会话结束清理
├── drainRunLoop.ts # macOS Run Loop 排空(动画帧同步)
├── appNames.ts # 已知应用 BundleID 映射
├── toolRendering.tsx # 工具调用结果的 React UI 渲染
└── wrapper.tsx # CU 工具的高层包装组件

三、Feature 门控机制(gates.ts)

Chicago Config 数据结构

type ChicagoConfig = CuSubGates & {
enabled: boolean // 总开关
coordinateMode: CoordinateMode // 'pixels' | 'normalized'
}

// 子开关(CuSubGates)
{
pixelValidation: boolean // 坐标越界校验
clipboardPasteMultiline: boolean // 多行粘贴支持
mouseAnimation: boolean // 鼠标动画效果
hideBeforeAction: boolean // 操作前隐藏无关窗口
autoTargetDisplay: boolean // 自动选择目标显示器
clipboardGuard: boolean // 剪贴板安全保护
}

开关读取链

GrowthBook Feature Flag: tengu_malort_pedway
↓ getDynamicConfig_CACHED_MAY_BE_STALE()
↓ 与 DEFAULTS 合并(partial JSON 安全)
↓ getChicagoEnabled() — 三重检查:
1. hasRequiredSubscription()(Max/Pro 或内部 ant)
2. readConfig().enabled
3. 内部用户的 MONOREPO_ROOT_DIR 排除(避免 dev 污染)

坐标模式冻结getChicagoCoordinateMode() 首次调用后冻结值,防止 GrowthBook 中途改变导致模型收到 “pixels” 指令但执行层按 “normalized” 换算。

订阅等级要求

function hasRequiredSubscription(): boolean {
if (process.env.USER_TYPE === 'ant') return true // 内部用户无门槛
const tier = getSubscriptionType()
return tier === 'max' || tier === 'pro' // 外部:Max/Pro 专属
}

四、MCP 集成架构(setup.ts + mcpServer.ts)

为什么用 MCP 而不是内置工具?

API 后端检测 mcp__computer-use__* 工具名
→ 自动在 System Prompt 注入 COMPUTER_USE_MCP_AVAILABILITY_HINT
→ 告诉模型"当前环境支持桌面操控"

如果用不同名字的内置工具,API 端不会注入这个提示,模型不知道能用。

MCP 配置构建流程

// setup.ts — 构建动态 MCP 配置
setupComputerUseMCP() {
// 1. 生成工具列表(按坐标模式)
const allowedTools = buildComputerUseTools(CLI_CU_CAPABILITIES, coordinateMode)
.map(t => buildMcpToolName('computer-use', t.name))
// → ['mcp__computer-use__screenshot', 'mcp__computer-use__left_click', ...]

// 2. 配置永远不会真正 spawn 的 stdio 进程
// mcpServer.ts 在进程内拦截,根本不走 stdio
return {
mcpConfig: { 'computer-use': { type: 'stdio', command: process.execPath, ... } },
allowedTools // 自动允许,绕过权限弹窗
}
}

In-Process 拦截mcpServer.ts 检测到服务名为 computer-use 时,直接在当前进程内处理工具调用,不真正启动子进程。


五、执行器核心(executor.ts)

截图流水线

getDisplayGeometry()          # 获取逻辑像素尺寸 + DPI 缩放因子

computeTargetDims() # 逻辑 → 物理 → API 目标尺寸

resolvePrepareCapture() # 确定截图参数(排除终端窗口)

prepareDisplay() # 准备显示环境(隐藏无关窗口)

drainRunLoop() # 等待 macOS 动画帧完成

takeScreenshot(quality=0.75) # JPEG 截图,质量 75%

API 接收 base64 图像

终端窗口排除

// common.ts — 检测当前终端 BundleID
getTerminalBundleId(): string | null {
// 优先读 LaunchServices 注入的环境变量
const cfBundleId = process.env.__CFBundleIdentifier
if (cfBundleId) return cfBundleId

// 降级:从 env.terminal 查内置映射表
return TERMINAL_BUNDLE_ID_FALLBACK[env.terminal ?? ''] ?? null
}

// 支持的终端: iTerm2, Apple Terminal, Ghostty, Kitty, Warp, VSCode

截图时将终端 BundleID 加入排除列表(captureExcluding),保证截图只包含目标应用。

剪贴板操作

// 读剪贴板
async readClipboardViaPbpaste(): Promise<string>

// 写剪贴板
async writeClipboardViaPbcopy(text: string): Promise<void>

六、坐标系统

两种坐标模式

模式 说明 适用场景
pixels 逻辑像素坐标(用户熟悉) 大多数操作
normalized 归一化 [0,1] 坐标 分辨率无关场景

坐标转换链

API 目标分辨率(targetImageSize 计算)
→ physW = logicalW × scaleFactor
→ [targetW, targetH] = API_RESIZE_PARAMS 约束
模型点击坐标(相对于 API 图像)
→ 还原到逻辑坐标
→ 传递给 @ant/computer-use-input 执行

七、安全机制

并发锁(computerUseLock.ts)

同一时刻只允许一个 CU 操作执行
→ 防止并发鼠标/键盘操作相互干扰
→ 超时自动释放,避免死锁

ESC 紧急中止(escHotkey.ts)

全局监听 ESC 键
→ 注册 notifyExpectedEscape() 告知系统当前 CU 操作
→ 用户按 ESC → 中止当前操作,恢复正常状态

TCC(系统隐私权限)

computer-use-swift 调用前检查 macOS TCC 权限:
- Accessibility(辅助功能)
- Screen Recording(屏幕录制)
未授权时 → request_access 工具引导用户授权

会话清理(cleanup.ts)

会话结束时:
→ 恢复被隐藏的窗口
→ 释放 computerUseLock
→ 清理截图临时文件

八、与工具系统的集成

用户消息 → QueryEngine

模型选择 mcp__computer-use__screenshot 工具

ToolExecutor 识别前缀 "mcp__computer-use__"

McpToolExecutor → 检查是否为 computer-use 服务

In-Process MCP Server(mcpServer.ts)处理

执行 executor.ts 对应方法

返回截图/操作结果 → 下一轮模型决策

九、关键设计决策

决策 原因
macOS 独占 Swift SCContentFilter + NSWorkspace API,Linux/Windows 无对应实现
MCP 而非内置工具 API 端根据工具名注入系统提示,名字不对就不触发
JPEG 而非 PNG 截图质量 75% 的 JPEG,大幅减少 token 消耗(图像 → base64 tokens)
坐标模式冻结 防止 GrowthBook 实验中途改变导致坐标系不一致
In-Process 拦截 避免 spawn 子进程的开销,computer-use 服务名作为路由键
终端作为代理宿主 CLI 无窗口,用终端 BundleID 代替 Electron 窗口做截图排除

涉及源文件

  • src/utils/computerUse/
打赏
  • 微信
  • 支付宝

评论